import json
import threading
import time
import requests
import pysvn
import re
import os
from shutil import copy, move


class ResultSender(threading.Thread):
    def __init__(self, logger, jobsToSendList, configData, svnUsername, svnPassword):
        threading.Thread.__init__(self)
        self._crumbLink = configData['jenkinsCrumbLink']
        self._svnRoot = configData['svnRoot']
        self._jobsToSendList = jobsToSendList
        self._isOnline = True
        self._isRunning = True
        self._logSwitch = False
        self._logger = logger
        self._svnUsername = svnUsername
        self._svnPassword = svnPassword
        self._headerLogInfo = "[" + "RESULTS_SENDER".rjust(19) + "]\t"
        self._raportCacheDir = 'report_cache'
        self._archiveFileName = 'map.txt'
        self._retries = 0
        self._tmsFile = None
        self.prepareSVNandFolders()
        self.start()

    def prepareSVNandFolders(self):
        if not os.path.isdir(self._raportCacheDir):
            self._logger.info(self._headerLogInfo + "Creating ./report_cache directory")
            os.makedirs(self._raportCacheDir)
        self._logger.debug(self._headerLogInfo + "Preparing SVN client data")
        self._svnClient = pysvn.Client()
        self._svnClient.callback_ssl_server_trust_prompt = self.ssl_server_trust_prompt
        self._svnClient.callback_get_login = self.getLogin
        self._svnClient.set_default_username(self._svnUsername)
        self._svnClient.set_default_password(self._svnPassword)

    def getHeaders(self):
        return {
            'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
        }

    def prepareData(self, projectName, branchName, testType, buildId, status, pegRetCode, message='', reportFile='',
                    messageType='results'):
        data = 'json=' + json.dumps({
            "parameter": {
                "name": messageType,
                "value": json.dumps({
                    "params": {
                        "project": projectName,
                        "branch": branchName,
                        "testType": testType,
                        "buildId": buildId,
                        "message": message,
                        "status": status,
                    },
                    "jsonrpc": "2.0",
                    "id": 0,
                    "tmsRepoFilePath": reportFile,
                    "pegRetCode": pegRetCode,
                })
            }
        }) + '&proceed=Proceed'
        return data

    def prepareParams(self):
        params = {
            'proceed': 'Proceed'
        }
        return params

    def prepareFiles(self, fileContent):
        files = {
            'file': ('raport.xml', fileContent)
        }
        return files

    def logQueueInfo(self, logEmpty):
        if not logEmpty and not self._logSwitch:
            self._logger.info(self._headerLogInfo + "Messages in queue")
            self._logSwitch = True
        if logEmpty and self._logSwitch:
            self._logger.info(self._headerLogInfo + "Messages queue empty, waiting for new messages")
            self._logSwitch = False

    def run(self):
        self._logger.info(self._headerLogInfo + "Results sender started")
        while(self._isRunning):
            time.sleep(0.5)
            if len(self._jobsToSendList._list) > 0:
                self.logQueueInfo(False)
                self._jobToSend = self._jobsToSendList.use()
                if self._jobToSend is not None:
                    self.send()
                    if self._jobToSend._retry >= 3:
                        self._logger.info(self._headerLogInfo + "{} Reached max tries (3) trying to send message ({})"
                                          .format(self._jobToSend._id, self._jobToSend._message) +
                                          " to Jenkins. Removing message from queue")
                        self._jobToSend.resetRetries()
                        self._jobsToSendList.remove(self._jobToSend)
                        self._tmsFile = None
            else:
                self.logQueueInfo(True)

    def send(self):
        try:
            response = self.sendResults()
            self._logger.info(self._headerLogInfo + "{} Message sent to Jenkins: {}"
                              .format(self._jobToSend._id, self._jobToSend._message))
            if self.handleResponse(response):
                self._jobsToSendList.remove(self._jobToSend)
                self._tmsFile = None
        except Exception as e:
            if self._jobToSend._retry <= 3:
                self._logger.exception(self._headerLogInfo + "{} Message not sent, Exception: {}"
                                       .format(self._jobToSend._id, str(e)))
                self._jobToSend.incrementRetries()

    def sendResults(self):
        self._logger.debug(self._headerLogInfo + "{} Sending message to jenkins: {}"
                           .format(self._jobToSend._id, self._jobToSend._message))
        if self._tmsFile is None:
            if self._jobToSend._tmsRepo is not None:
                self._tmsFile = self.uploadReportFileToTms()
            else:
                self._tmsFile = 'Stored in archive'
        return self.sendResultsToJenkins()

    def getLogin(self, realm, username, may_save):
        return True, self._svnUsername, self._svnPassword, may_save

    def ssl_server_trust_prompt(self, trust_dict):
        return True, trust_dict['failures'], False

    def uploadReportFileToTms(self):
        self._logger.debug(self._headerLogInfo + "{} Uploading report file to tms repo ({})"
                           .format(self._jobToSend._id, self._jobToSend._tmsRepo))
        name = None
        while (name == None):
            fileName, repositoryFileName = self.prepareFileNames(self._jobToSend._tmsRepo,
                                                                 self._jobToSend._job._testType,
                                                                 self._jobToSend._file)
            if fileName:
                name = self.uploadFile(fileName, repositoryFileName)
            else:
                name = 'Report file does not exist!'
                self._logger.warning(self._headerLogInfo + "{} Cannot upload file: {} to tms repo. File does not exist!"
                                     .format(self._jobToSend._id, fileName, self._jobToSend._tmsRepo))
        return name

    def uploadFile(self, fileName, repositoryFileName):
        try:
            self._logger.debug(self._headerLogInfo + "{} Importing file: '{}' to tms repo as: {}"
                               .format(self._jobToSend._id, fileName, repositoryFileName))
            self._svnClient.import_(fileName, repositoryFileName, "CI_Agent: update TMS repository")
            os.remove(fileName)
            self._logger.debug(self._headerLogInfo + "{} File imported and deleted from local folder"
                               .format(self._jobToSend._id))
            self._isFileUploaded = True
            name = '{}{}'.format(self._jobToSend._tmsRepo, fileName)
            self.uploadCachedFiles()
            self._jobToSend._tmsRepo = None
        except Exception as e:
            if e == pysvn.ClientError and 'already exists' in str(e):
                self._logger.error(self._headerLogInfo + "{} Error while importig file to tms. "
                                   .format(self._jobToSend._id) + "File already exists. Retrying with new name...")
                name = None
            else:
                self._logger.exception(self._headerLogInfo + "{} Error while importig file to tms: {}"
                                       .format(self._jobToSend._id, str(e)))
                cachedFileName = self.archiveReportFile(fileName)
                os.remove(fileName)
                name = "Report has been cached localy as '{}'!".format(cachedFileName)
        return name

    def uploadCachedFiles(self):
        self._logger.debug(self._headerLogInfo + "{} Uploading cached files".format(self._jobToSend._id))
        files = self.findFiles(self._raportCacheDir)
        for item in files:
            if self.uloadCachedFileToTms(item):
                os.remove(item)

    def uloadCachedFileToTms(self, file):
        self._logger.debug(self._headerLogInfo + "{} Uploading cached file: {}".format(self._jobToSend._id, file))
        pathSteps = file[file.find(self._raportCacheDir) + len(self._raportCacheDir) + 1:].split(os.sep)
        suffix = pathSteps[-1][:2]
        del pathSteps[-1]
        svnPath = self.getSvnPath(pathSteps)
        fileName, repositoryFileName = self.prepareFileNames(svnPath, suffix, file)
        toReturn = False
        if fileName:
            try:
                self._logger.debug(self._headerLogInfo + "{} Importing file: '{}' to tms repo as: {}"
                                   .format(self._jobToSend._id, fileName, repositoryFileName))
                self._svnClient.import_(fileName, repositoryFileName, "CI_Agent: update TMS repository")
                os.remove(fileName)
                self._logger.debug(self._headerLogInfo + "{} File imported and deleted from local folder"
                                   .format(self._jobToSend._id))
                toReturn = True
            except Exception as e:
                self._logger.exception(self._headerLogInfo + "{} Error while importig file to tms: {}"
                                       .format(self._jobToSend._id, str(e)))
                toReturn = False
        else:
            self._logger.exception(self._headerLogInfo + "{} No report file to upload to tms repo".format(self._jobToSend._id))
        return toReturn

    def getSvnPath(self, pathSteps):
        self._logger.debug(self._headerLogInfo + "{} Preparing SVN path for cached file".format(self._jobToSend._id))
        path = self._svnRoot
        for item in pathSteps:
            path = '{}{}/'.format(path, item)
        self._logger.debug(self._headerLogInfo + "{} SVN path set to: {}".format(self._jobToSend._id, path))
        return path

    def findFiles(self, path):
        self._logger.debug(self._headerLogInfo + "{} Looking for cached files in: {}".format(self._jobToSend._id, path))
        listOfFiles = []
        for root, subdirs, files in os.walk(path):
            for file in files:
                self._logger.debug(self._headerLogInfo + "{} Found cached file: {}".format(self._jobToSend._id, file))
                listOfFiles.append(os.path.realpath(os.path.join(root, file)))
        return listOfFiles

    def prepareFileNames(self, tmsRepo, suffix, reportFile):
        listOfFileReports = self.getAllFilesInRepo(tmsRepo)
        self._logger.debug(self._headerLogInfo + "{} Preparing file name and tms repository filename for report file"
                           .format(self._jobToSend._id))
        number = self.nextNumber("^[rR]aport([0-9]+)_{0}.xml".format(suffix), listOfFileReports)  # {0} = suffix
        fileName = "Raport{}_{}.xml".format(number, suffix)
        self._logger.debug(self._headerLogInfo + "{} Local filename for report file: {}"
                           .format(self._jobToSend._id, fileName))
        repositoryFileName = '{}{}'.format(tmsRepo, fileName)
        self._logger.debug(self._headerLogInfo + "{} Tms repo filename for report file: {}"
                           .format(self._jobToSend._id, repositoryFileName))
        if os.path.isfile(reportFile) and reportFile.endswith('xml'):
            copy(reportFile, fileName)
            self._logger.debug(self._headerLogInfo + "{} Original report file: {} moved to new file: {}"
                               .format(self._jobToSend._id, reportFile, fileName))
            return fileName, repositoryFileName
        else:
            self._logger.warning(self._headerLogInfo + "{} Original report file: {} does not exist!"
                                 .format(self._jobToSend._id, reportFile))
            return '', repositoryFileName

    def getAllFilesInRepo(self, tmsRepo):
        self._logger.debug(self._headerLogInfo + "{} Listing tms repository: '{}', for list of files"
                           .format(self._jobToSend._id, tmsRepo))
        listOfFileReports = []
        if tmsRepo is not None:
            listOfFileReportsPaths = self._svnClient.list(tmsRepo)
        for file in listOfFileReportsPaths:
            listOfFileReports.append(str(file[0].repos_path).split('/')[-1])
        return listOfFileReports

    def archiveReportFile(self, fileName):
        self._logger.debug(self._headerLogInfo + "{} Caching report file: '{}' to local folder"
                           .format(self._jobToSend._id, fileName))
        tmsRepo = self._jobToSend._tmsRepo
        if self._svnRoot in tmsRepo:
            relativePath = tmsRepo[len(self._svnRoot):]
        else:
            relativePath = os.path.join(self._raportCacheDir, 'unknownRepo')
        localRaportCachePath = os.path.join(self._raportCacheDir, relativePath)
        self._logger.debug(self._headerLogInfo + "{} Local folder set to: {}".format(self._jobToSend._id, localRaportCachePath))
        if not os.path.isdir(localRaportCachePath):
            os.makedirs(localRaportCachePath)
        listOfCachedFiles = os.listdir(localRaportCachePath)
        number = self.nextNumber("^{}([0-9])+.xml".format(self._jobToSend._job._project), listOfCachedFiles)
        newCacheFileName = '{}{}{}'.format(self._jobToSend._job._testType, number, '.xml')
        self._logger.debug(self._headerLogInfo + "{} Cached report filename set to: {}"
                           .format(self._jobToSend._id, newCacheFileName))
        localFileName = os.path.join(localRaportCachePath, newCacheFileName)
        move(fileName, localFileName)
        self._logger.debug(self._headerLogInfo + "{} Report file saved to local folder as: {}"
                           .format(self._jobToSend._id, localFileName))
        return localFileName

    def nextNumber(self, regex, filesList):
        self._logger.debug(self._headerLogInfo + "{} Looking for next number for actual report file"
                           .format(self._jobToSend._id))
        pattern = re.compile(regex)
        filtered_files = filter(pattern.match, filesList)
        latest_number = 0
        for entry in filtered_files:
            curr_entry = int(pattern.match(entry).group(1))
            if latest_number < curr_entry:
                latest_number = curr_entry
        self._logger.debug(self._headerLogInfo + "{} Next number value: {}".format(self._jobToSend._id, latest_number + 1))
        return latest_number + 1

    def updateHeaders(self, headers, key, crumb):
        headers[key] = crumb

    def sendResultsToJenkins(self):
        headers = self.getHeaders()
        crumbResp = requests.get(self._crumbLink, headers=headers, timeout=0.5)
        crumbKey = crumbResp.text.split(':')[0]
        crumbValue = crumbResp.text.split(':')[1]
        data = self.prepareResponse()
        self.updateHeaders(headers, crumbKey, crumbValue)
        return requests.post(self._jobToSend._job._resultJob, data=data, headers=headers, timeout=0.5)

    def prepareResponse(self):
        self._logger.debug(self._headerLogInfo + "{} Preparing data for result message sent to Jenkins"
                           .format(self._jobToSend._id))
        if self._jobToSend._returnCode is not None:
            data = self.prepareData(self._jobToSend._job._project, self._jobToSend._job._branch, self._jobToSend._job._testType,
                                    self._jobToSend._job._buildId, 0, self._jobToSend._returnCode, self._jobToSend._message,
                                    self._tmsFile)
        else:
            data = self.prepareData(self._jobToSend._job._project, self._jobToSend._job._branch, self._jobToSend._job._testType,
                                    self._jobToSend._job._buildId, 0, self._jobToSend._returnCode, self._jobToSend._message,
                                    self._tmsFile, 'wait-end')
        self._logger.debug(self._headerLogInfo + "{} Response data to be sent: {}".format(self._jobToSend._id, data))
        return data

    def handleResponse(self, response):
        self._logger.debug(self._headerLogInfo + "{} Handling response after sending message to Jenkins"
                           .format(self._jobToSend._id))
        codes = {
            200: "{} Sending Message: OK".format(self._jobToSend._id),
            201: "{} Sending Message: Created".format(self._jobToSend._id),
            500: "{} Sending Message: Error, Message refused: code {}".format(self._jobToSend._id, response.status_code)
        }
        info = codes.get(response.status_code, "{} Error while sending message to jenkins, resultCode: {}"
                         .format(self._jobToSend._id, str(response.status_code)))
        if "Error" in info:
            self._logger.error(self._headerLogInfo + info + str(response.content))
            self._jobToSend.incrementRetries()
            return False
        else:
            self._logger.info(self._headerLogInfo + info)
            return True

    def destroy(self):
        self._isRunning = False
